﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

using BizElements.Core;
using BizElements.TemplateEngine;

namespace BizElements.Generator.OrmComponents
{
    /// <summary>Enumerates DB/ORM input component types.</summary>
    public static class DbComponentType
    {
        #region Predefined types.

        /// <summary>Instance property contains <see cref="BizElements.Generator.Table"/> object.</summary>
        public static readonly ComponentType Table = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.TableField"/> object.</summary>
        public static readonly ComponentType Field = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.ForeignKey"/> object.</summary>
        public static readonly ComponentType FK = new ComponentType(Guid.NewGuid());
        /// <summary>Instance contains the name (string) of the column that is a part of the foreign key.</summary>
        public static readonly ComponentType FkColumnName = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.Table"/> object.</summary>
        public static readonly ComponentType ChildTable = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains <see cref="BizElements.Generator.ForeignKey"/> object.</summary>
        public static readonly ComponentType ChildRelation = new ComponentType(Guid.NewGuid());
        /// <summary>Instance property contains an array of <see cref="BizElements.Generator.Table"/> objects.</summary>
        public static readonly ComponentType TableArray = new ComponentType(Guid.NewGuid());

        #endregion
    }

    /// <summary>Builds <see cref="DbComponentType.Table"/> and <see cref="DbComponentType.TableArray"/> input components.</summary>
    public sealed class DbComponentBuilder : ITemplateInputComponentBuilder
    {
        #region ITemplateInputComponentBuilder.

        /// <summary>Builds <see cref="DbComponentType.Table"/> and <see cref="DbComponentType.TableArray"/> input components.</summary>
        public ITemplateInputComponent Build(object instance)
        {
            Table table = instance as Table;
            if (table != null)
                return new DbComponent(table);

            Table[] tableArray = instance as Table[];
            if (tableArray != null)
                return new DbComponent(tableArray);
            
            throw new ArgumentException("Only Table and Table[] objects are supported.", "instance");
        }

        /// <summary>Checks that the provided object is <see cref="BizElements.Generator.Table"/> object or an array of <see cref="BizElements.Generator.Table"/> objects.</summary>
        public bool IsCompatibleItem(object instance)
        {
            bool isTableOrTableArray = (instance is Table) || (instance is Table[]);
            return isTableOrTableArray;
        }

        #endregion
    }

    /// <summary>DB/ORM input component. One of the types enumerated in <see cref="DbComponentType"/> class.</summary>
    public sealed class DbComponent : TemplateInputComponentBase
    {
        #region CTor.

        /// <summary>Creates a root component. Instance property contains <see cref="BizElements.Generator.Table"/> object.</summary>
        public DbComponent(Table table)
            : base(DbComponentType.Table, table, table.TableName, null, null)
        {
        }

        /// <summary>Creates a root component. Instance property contains an array of <see cref="BizElements.Generator.Table"/> objects.</summary>
        public DbComponent(Table[] tables)
            : base(DbComponentType.TableArray, tables, "-", null, null)
        {
        }

        private DbComponent(ComponentType type, object obj, string name, string fullName, ITemplateInputComponent parent)
            : base(type, obj, name, fullName, parent)
        {
        }

        #endregion        

        #region Ensure/build sub-components list.

        /// <summary>Ensures that Subcomponents property is initialized.</summary>
        protected override void EnsureSubcomponents()
        {
            if (this.Subcomponents != null)
                return;

            this.Subcomponents = new List<ITemplateInputComponent>();
            AppendSubComponentsForTable();
            AppendSubComponentsForFK();
            AppendSubComponentsForTableArray();
        }

        private void AppendSubComponentsForTable()
        {
            Table tbl = this.Instance as Table;
            if (tbl != null)
            {
                foreach (TableField field in tbl.Fields)
                {
                    string fullFieldPath = tbl.TableName + "." + field.ColumnName;
                    this.Subcomponents.Add(new DbComponent(DbComponentType.Field, field, field.ColumnName, fullFieldPath, this));
                }

                foreach (ForeignKey fk in tbl.ForeignKeys)
                {
                    string fullPath = tbl.TableName + "." + fk.ConstraintName;
                    this.Subcomponents.Add(new DbComponent(DbComponentType.FK, fk, fk.ConstraintName, fullPath, this));
                }

                foreach (Table child in tbl.GetChildTables())
                    this.Subcomponents.Add(new DbComponent(DbComponentType.ChildTable, child, child.TableName, null, this));

                foreach (ForeignKey childFk in tbl.GetChildRelations())
                {
                    string fullPath = null;
                    if (childFk.ChildTable != null)
                        fullPath = childFk.ChildTable.TableName + "." + childFk.ConstraintName;

                    this.Subcomponents.Add(new DbComponent(DbComponentType.ChildRelation, childFk, childFk.ConstraintName, fullPath, this));
                }
            }
        }

        private void AppendSubComponentsForFK()
        {
            ForeignKey fk = this.Instance as ForeignKey;
            if (fk != null)
            {
                foreach (string fkPart in fk.ChildColumns)
                {
                    string fullColumnPath = null;
                    if (fk.ChildTable != null)
                        fullColumnPath = fk.ChildTable.TableName + "." + fkPart;

                    this.Subcomponents.Add(new DbComponent(DbComponentType.FkColumnName, fkPart, fkPart, fullColumnPath, this));
                }
            }
        }

        private void AppendSubComponentsForTableArray()
        {
            Table[] allTables = this.Instance as Table[];
            if (allTables != null)
            {
                foreach (Table tbl in allTables)
                    this.Subcomponents.Add(new DbComponent(DbComponentType.Table, tbl, tbl.TableName, null, this));
            }
        }

        #endregion

        #region GetComponentKeys.

        /// <summary>Gets component keys. Name is returned for tables, fields and child tables. 
        /// Child table name is returned for child relations.
        /// Parent table and all FK field names are returned for FKs.
        /// For table array <b>null</b> is returned.</summary>
        /// <returns>Array of string with one item or <b>null</b>.</returns>
        protected override string[] GetComponentKeys()
        {
            if ((this.Type == DbComponentType.Table) || (this.Type == DbComponentType.ChildTable) || (this.Type == DbComponentType.FkColumnName))
            {
                return new string[] { this.Name };
            }
            else if (this.Type == DbComponentType.Field)
            {
                return new string[] { this.Name, this.Parent.Name + "." + this.Name };
            }
            else if (this.Type == DbComponentType.ChildRelation)
            {
                return new string[] { ((ForeignKey)this.Instance).ChildTable.TableName };
            }
            else if (this.Type == DbComponentType.FK)
            {
                ForeignKey fk = (ForeignKey)this.Instance;
                List<string> parentTableNameAndFkColumnNames = new List<string>();
                parentTableNameAndFkColumnNames.Add(fk.ParentTableName);
                foreach (string colName in fk.ChildColumns)
                    parentTableNameAndFkColumnNames.Add(fk.ChildTable.TableName + "." + colName);

                return parentTableNameAndFkColumnNames.ToArray();
            }
            else
            {
                return null;
            }
        }

        #endregion
    }

    /// <summary>Enumerates variables and settings that influence generator behavior.</summary>
    public static class CommonVariables
    {
        /// <summary>Parent suffix. Default is "Parent".</summary>
        public const string ParentSuffix = "ParentSuffix";
        /// <summary>Recursive parent suffix. Default is "Superior".</summary>
        public const string RecursiveParentSuffix = "RecursiveParentSuffix";
        /// <summary>Parallel parent suffix. Default is "Parent".</summary>
        public const string ParallelParentSuffix = "ParallelParentSuffix";
    }

    /// <summary>Contains extension methods for DB table input components which may be used in templates.</summary>
    public static class TableExtensionMethods
    {
        #region Table input component extension methods.

        /// <summary>Gets generated class prefix for a table/view. Pascal case name convention is enforced, thus table MY_TABLE will have MyTable class prefix.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static string ClassPrefix(this ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            string className = DbComponentExtensionMethods.GetClassPrefix(tbl.TableName, table.CurrentTemplateData);
            return className;
        }

        ///// <summary>Gets generated class prefix for a table/view. Pascal case name convention is enforced, thus table MY_TABLE will have MyTable class prefix.</summary>
        ///// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        ///// <param name="semicolDelimitedParentPrefixesToRemove">Semicolon delimited parent table prefixes to remove.</param>
        //public static string ClassPrefix(this ITemplateInputComponent table, string semicolDelimitedParentPrefixesToRemove)
        //{
        //    Table tbl = (Table)table.Instance;
        //    string className = DbComponentExtensionMethods.GetClassPrefix(tbl.TableName, table.CurrentTemplateData);
        //    string[] removedPrefixes = FkExtensionMethods.ParseSemicolDelimitedArray(semicolDelimitedParentPrefixesToRemove);
        //    className = RemovePrefix(className, semicolDelimitedParentPrefixesToRemove.Split(';'));

        //    return className;
        //}

        //private static string RemovePrefix(string originalName, string[] prefixes)
        //{
        //    if ((prefixes == null) || (prefixes.Length == 0))
        //        return originalName;

        //    // Case insensitive.
        //    string lowerName = originalName.ToLower();
        //    foreach (string prefix in prefixes)
        //    {
        //        if (lowerName.StartsWith(prefix.ToLower()))
        //        {
        //            string nameWithoutPrefix = originalName.Substring(prefix.Length);
        //            return nameWithoutPrefix;
        //        }
        //    }

        //    return originalName;
        //}

        /// <summary>Get the number of fields in the table.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static int FieldCount(this ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            return tbl.Fields.Length;
        }

        /// <summary>Gets the number of foreign keys for the table.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static int FkCount(this ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            // Count FKs from tbl.ForeignKeys where exists table from tbl.GeneratedTable where TableName = fk.ParentTableName.
            int generatedParentTablesCount = ArrayUtil.Count<ForeignKey>(tbl.ForeignKeys, (fk) => tbl.IsTableGenerated(fk.ParentTableName));
            return generatedParentTablesCount;
        }

        /// <summary>Gets the number of PK fields in the table.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static int PkCount(this ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            return tbl.NumberOfPrimaryKeyFields;
        }

        /// <summary>Tries to get first textual property name or if it's not found any other non-key property.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static string GetFirstTextOrNonKeyPropertyName(this ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            foreach (TableField field in tbl.Fields)
            {
                if (field.Types.NativeDbType.Contains("char"))
                    return field.Names.PublicMember;
            }

            foreach (TableField field in tbl.Fields)
            {
                bool isKeyField = (field.IsPrimaryKeyPart || field.IsForeignKeyPart);
                if (!isKeyField)
                    return field.Names.PublicMember;
            }

            return tbl.Fields[0].Names.PublicMember;
        }

        #endregion
    }

    /// <summary>Contains extension methods for DB field input components which may be used in templates.</summary>
    public static class FieldExtensionMethods
    {
        #region Field input component extension methods.

        /// <summary>Gets generated local variable name for a column - camel case.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string LocalVariableName(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).Names.LocalVariable;
        }

        /// <summary>Gets generated private mameber/field name for a column - camel case.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string PrivateMemberName(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).Names.PrivateMember;
        }

        /// <summary>Gets generated protected property name for a column - pascal case prefixed with underscore character.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string ProtectedMemberName(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).Names.ProtectedMember;
        }

        /// <summary>Gets generated public property name for a column - pascal case.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string PublicMemberName(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).Names.PublicMember;
        }

        /// <summary>Gets generated nullable type for a column. Eg. in C# for values types '?' character is appended to type name.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string NullableType(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).Types.NullableType;
        }

        /// <summary>Gets generated type for a column.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string Type(this ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return (field.IsNullable) ? field.Types.NullableType : field.Types.Type;
        }

        /// <summary>Gets generated expression that retrieves a value from the nullabe variable. Eg. in C# for nullable value type ".Value" is generated.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string ValueExpressionSuffix(this ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return field.Types.GetValueExpression("", field.Types.IsValueType);
        }

        /// <summary>Gets generated conversion or cast expression that converts the specified expression to data type of a column.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        /// <param name="expressionThatIsToBeConverted">Expression/variable that is to be converted.</param>
        public static string CastExpression(this ITemplateInputComponent tableField, string expressionThatIsToBeConverted)
        {
            TableField field = (TableField)tableField.Instance;
            return field.Types.CastExpression(expressionThatIsToBeConverted);

        }

        /// <summary>Gets an expression that converts array element with the same index/position as column to data type of a column. Eg. Convert.ToInt32(values[0]) for the 1st table column.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        /// <param name="arrayVariable">Array variable that contains values for all fields of the table.</param>
        public static string ConvertFromArrayAtIndex(this ITemplateInputComponent tableField, string arrayVariable)
        {
            TableField field = (TableField)tableField.Instance;
            string expressionThatIsToBeConverted = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", arrayVariable, field.Index);
            return field.Types.CastExpression(expressionThatIsToBeConverted);
        }

        /// <summary>Gets column name.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string ColumnName(this ITemplateInputComponent tableField)
        {
            return ((TableField)tableField.Instance).ColumnName;
        }

        //new DbColumnConfiguration(ColumnNames[0], DbType.Int32, typeof(int), false, 
        //  0, true, null, 0, true, false, 
        //  false, "CountryId", int.MinValue, int.MaxValue, false, SequenceNameOverrides[0])
        const string DbColumnConfigurationCtorFormat = "new DbColumnConfiguration({0}, DbType.{1}, typeof({2}), {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, \"{11}\", {12}, {13}, {14}, SequenceNameOverrides[{15}])";

        /// <summary>Generates code (hard coded) that creates a new instance of <see cref="BizElements.Core.DbColumnConfiguration"/> class for a column.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static string NewDbColumnConfiguration(this ITemplateInputComponent tableField)
        {
            TableField f = (TableField)tableField.Instance;
            int fieldIdx = f.Index;
            string colName = string.Format(CultureInfo.InvariantCulture, "ColumnNames[{0}]", fieldIdx);
            string autoIncr = (f.AutoIncrement) ? "true" : string.Format(CultureInfo.InvariantCulture, "AutoIncrementOverrides[{0}] ?? {1}", fieldIdx, f.AutoIncrement.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
            string maxLength = (f.IsMaxType) ? (int.MaxValue / 2).ToString(CultureInfo.InvariantCulture) : f.Length.ToString(CultureInfo.InvariantCulture);
            string minVal = f.Types.MinNativeValue;
            string maxVal = f.Types.MaxNativeValue;
            return string.Format(CultureInfo.InvariantCulture, DbColumnConfigurationCtorFormat, colName, f.Types.DbType, f.Types.Type, f.IsNullable.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
                fieldIdx, autoIncr, "null", maxLength, f.IsPrimaryKeyPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), f.IsForeignKeyPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
                f.IsUniqueConstraintPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), PublicMemberName(tableField), minVal, maxVal, f.IsAutoGenerated.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), fieldIdx);
        }

        /// <summary>Generates code (hard coded) that creates a new instance of <see cref="BizElements.Core.DbColumnConfiguration"/> class for a column.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        /// <param name="mappedTypeOverride">Manually defined .NET type.</param>
        public static string NewDbColumnConfiguration(this ITemplateInputComponent tableField, string mappedTypeOverride)
        {
            TableField f = (TableField)tableField.Instance;
            int fieldIdx = f.Index;
            string colName = string.Format(CultureInfo.InvariantCulture, "ColumnNames[{0}]", fieldIdx);
            string autoIncr = (f.AutoIncrement) ? "true" : string.Format(CultureInfo.InvariantCulture, "AutoIncrementOverrides[{0}] ?? {1}", fieldIdx, f.AutoIncrement.ToString(CultureInfo.InvariantCulture).ToLowerInvariant());
            string maxLength = (f.IsMaxType) ? (int.MaxValue / 2).ToString(CultureInfo.InvariantCulture) : f.Length.ToString(CultureInfo.InvariantCulture);
            string minVal = f.Types.MinNativeValue;
            string maxVal = f.Types.MaxNativeValue;
            string dotNetType = string.IsNullOrEmpty(mappedTypeOverride) ? f.Types.Type : mappedTypeOverride;
            return string.Format(CultureInfo.InvariantCulture, DbColumnConfigurationCtorFormat, colName, f.Types.DbType, dotNetType, f.IsNullable.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
                fieldIdx, autoIncr, "null", maxLength, f.IsPrimaryKeyPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), f.IsForeignKeyPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
                f.IsUniqueConstraintPart.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), PublicMemberName(tableField), minVal, maxVal, f.IsAutoGenerated.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), fieldIdx);
        }

        /// <summary>Gets column index in the parent table.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static int GetFieldIndex(this ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return field.Index;
        }

        /// <summary>Gets PK part zero based index or -1 if input table/view does not have a primary key.</summary>
        /// <param name="pkField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static int GetPkPartIndex(this ITemplateInputComponent pkField)
        {
            Table tbl = (Table)pkField.Root.Instance;
            TableField pkPart = (TableField)pkField.Instance;
            if (!pkPart.IsPrimaryKeyPart)
                return -1;

            int idx = 0;
            foreach (TableField field in tbl.Fields)
            {
                if (field.ColumnName == pkPart.ColumnName)
                    return idx;
                else if (field.IsPrimaryKeyPart)
                    // Is other PK part.
                    idx++;
            }

            throw new InvalidOperationException("Should not get here.");
        }

        #endregion
    }

    /// <summary>Contains extension methods for DB foreign key input components which may be used in templates.</summary>
    public static class FkExtensionMethods
    {
        #region FK input component extension methods.

        /// <summary>Gets FK property name in the generated meta/table classes which contains a <see cref="BizElements.Core.DbRelation"/> object. Typically, property names have a "FK_" prefix.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string FkPropertyName(this ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            return GetFkPropertyName(fk);
        }

        /// <summary>Gets generated prefix of class that maps to parent table in the provided relation/FK.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string ParentClassPrefix(this ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            return DbComponentExtensionMethods.GetClassPrefix(fk.ParentTableName, foreignKey.CurrentTemplateData);
        }

        /// <summary>Gets the names of the protected property in child entity that contains a reference to parent entity.</summary>
        /// <param name="childFkColumnName">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contains the name (string) of a FK part child column name and
        /// <see cref="ITemplateInputComponent.Parent"/> contains <see cref="BizElements.Generator.ForeignKey"/> instance.</param>
        public static string ProtectedChildFkProperty(this ITemplateInputComponent childFkColumnName)
        {
            string fkPartColName = childFkColumnName.Name;
            Table childTable = (Table)childFkColumnName.Root.Instance;
            TableField fkPart = ArrayUtil.Find<TableField>(childTable.Fields, (f) => (f.ColumnName == fkPartColName));

            return fkPart.Names.ProtectedMember;
        }

        /// <summary>Gets the names of the public property in child entity that contains a reference to parent entity.</summary>
        /// <param name="childFkColumnName">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contains the name (string) of a FK part child column name and
        /// <see cref="ITemplateInputComponent.Parent"/> contains <see cref="BizElements.Generator.ForeignKey"/> instance.</param>
        public static string PublicChildFkProperty(this ITemplateInputComponent childFkColumnName)
        {
            string fkPartColName = childFkColumnName.Name;
            Table childTable = (Table)childFkColumnName.Root.Instance;
            TableField fkPart = ArrayUtil.Find<TableField>(childTable.Fields, (f) => (f.ColumnName == fkPartColName));

            return fkPart.Names.PublicMember;
        }

        /// <summary>Gets generated data type for protected property mapped to FK part/column in generated child entity class.</summary>
        /// <param name="childFkColumnName">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contains the name (string) of a FK part child column name and
        /// <see cref="ITemplateInputComponent.Parent"/> contains <see cref="BizElements.Generator.ForeignKey"/> instance.</param>
        public static string PublicChildFkPropertyDataType(this ITemplateInputComponent childFkColumnName)
        {
            string fkPartColName = childFkColumnName.Name;
            Table childTable = (Table)childFkColumnName.Root.Instance;
            TableField fkPart = ArrayUtil.Find<TableField>(childTable.Fields, (f) => (f.ColumnName == fkPartColName));

            if (fkPart.IsNullable)
                return fkPart.Types.NullableType;
            else
                return fkPart.Types.Type;
        }

        /// <summary>Gets the name of parent entity PK part property to which child FK part points to.</summary>
        /// <param name="childFkColumnName">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contains the name (string) of a FK part child column name and
        /// <see cref="ITemplateInputComponent.Parent"/> contains <see cref="BizElements.Generator.ForeignKey"/> instance.</param>
        public static string PublicParentPkProperty(this ITemplateInputComponent childFkColumnName)
        {
            string fkPartColName = childFkColumnName.Name;
            ForeignKey fk = (ForeignKey)childFkColumnName.Parent.Instance;
            Table childTable = (Table)childFkColumnName.Root.Instance;

            return GetPublicParentPkProperty(fkPartColName, fk, childTable);
        }

        /// <summary>Gets private entity class member that references parent entity object. Camel case.</summary>
        /// <param name="fkOrField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contain an instance of <see cref="BizElements.Generator.ForeignKey"/> object
        /// or <see cref="BizElements.Generator.TableField"/> that belong to child table.</param>
        /// <param name="semicolDelimitedParentPrefixesToRemove">Semicolon delimited parent table prefixes to remove.</param>
        public static string FkPrivateMemberName(this ITemplateInputComponent fkOrField, string semicolDelimitedParentPrefixesToRemove)
        {
            ForeignKey fk;
            Table tbl;
            GetTableAndFkFromInputComponent(fkOrField, out fk, out tbl);

            string[] removedPrefixes = ParseSemicolDelimitedArray(semicolDelimitedParentPrefixesToRemove);
            if (IsRecursiveRelation(fk))
            {
                string suffix = GetRecursiveParentSuffix(fkOrField.CurrentTemplateData);
                return RecursiveTemplate.RenderParentMember(fk, NetClassItem.PrivateMember, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
            else if (IsParallelRelation(tbl, fk.ParentTableName))
            {
                string suffix = GetParallelParentSuffix(fkOrField.CurrentTemplateData);
                return ParallelFkTemplate.RenderParentMember(fk, NetClassItem.PrivateMember, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
            else
            {
                string suffix = GetParentSuffix(fkOrField.CurrentTemplateData);
                return ParentTemplate.RenderParentMember(fk, NetClassItem.PrivateMember, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
        }

        /// <summary>Gets public entity class member that references parent entity object. Pascal case.</summary>
        /// <param name="fkOrField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property contain an instance of <see cref="BizElements.Generator.ForeignKey"/> object
        /// or <see cref="BizElements.Generator.TableField"/> that belong to child table.</param>
        /// <param name="semicolDelimitedParentPrefixesToRemove">Semicolon delimited parent table prefixes to remove.</param>
        public static string FkPublicMemberName(this ITemplateInputComponent fkOrField, string semicolDelimitedParentPrefixesToRemove)
        {
            ForeignKey fk;
            Table tbl;
            GetTableAndFkFromInputComponent(fkOrField, out fk, out tbl);

            string[] removedPrefixes = ParseSemicolDelimitedArray(semicolDelimitedParentPrefixesToRemove);
            if (IsRecursiveRelation(fk))
            {
                string suffix = GetRecursiveParentSuffix(fkOrField.CurrentTemplateData);
                return RecursiveTemplate.RenderParentMember(fk, NetClassItem.PublicProperty, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
            else if (IsParallelRelation(tbl, fk.ParentTableName))
            {
                string suffix = GetParallelParentSuffix(fkOrField.CurrentTemplateData);
                return ParallelFkTemplate.RenderParentMember(fk, NetClassItem.PublicProperty, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
            else
            {
                string suffix = GetParentSuffix(fkOrField.CurrentTemplateData);
                return ParentTemplate.RenderParentMember(fk, NetClassItem.PublicProperty, DbComponentExtensionMethods.ClassItemTemplate, removedPrefixes, suffix);
            }
        }

        /// <summary>Gets zero based FK index from table it belongs to.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static int GetFkIndex(this ITemplateInputComponent foreignKey)
        {
            ForeignKey inputFk = (ForeignKey)foreignKey.Instance;
            return inputFk.Index;
        }

        /// <summary>Generates code (hard coded) that creates a new instance of <see cref="BizElements.Core.DbRelation"/> class for the given foreign key.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="semicolDelimitedParentPrefixesToRemove">Semicolon delimited parent table prefixes to remove.</param>
        public static string NewDbRelation(this ITemplateInputComponent foreignKey, string semicolDelimitedParentPrefixesToRemove)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table tbl = (Table)foreignKey.Root.Instance;

            // new DbRelation(pomProcesiSustava, new IDbColumn[]{pomProcesiSustava.IdProcesa}, this, new IDbColumn[]{this.IdProcesa}, "FK__POM_ROLE___id_pr__7CD98669")
            StringBuilder ctor = new StringBuilder();
            ctor.Append("new DbRelation(");

            string parentTableVariable = FkPrivateMemberName(foreignKey, semicolDelimitedParentPrefixesToRemove);
            ctor.Append(parentTableVariable);

            List<string> parentPkPropertyPaths = new List<string>();
            List<string> childFkPropertyPaths = new List<string>();
            foreach (string fkPart in fk.ChildColumns)
            {
                var fkColumn = ArrayUtil.Find<TableField>(tbl.Fields, (f) => (f.ColumnName == fkPart));
                childFkPropertyPaths.Add(string.Format(CultureInfo.InvariantCulture, "this.{0}", fkColumn.Names.PublicMember));
                parentPkPropertyPaths.Add(string.Format(CultureInfo.InvariantCulture, "{0}.{1}", parentTableVariable, GetPublicParentPkProperty(fkPart, fk, tbl)));
            }

            ctor.Append(", new IDbColumn[] { ");
            ctor.Append(ArrayUtil.Concat(parentPkPropertyPaths, ", "));

            ctor.Append(" }, this, new IDbColumn[] { ");
            ctor.Append(ArrayUtil.Concat(childFkPropertyPaths, ", "));

            ctor.Append(" }, \"");
            ctor.Append(fk.ConstraintName);
            ctor.Append("\")");

            return ctor.ToString();
        }

        /// <summary>Generates code (hard coded) that creates a new instance of <see cref="BizElements.Core.DbRelationConfiguration"/> class for the given foreign key.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="semicolDelimitedParentPrefixesToRemove">Semicolon delimited parent table prefixes to remove.</param>
        public static string DbRelationConfigurationCtorArgs(this ITemplateInputComponent foreignKey, string catalogClassFullName)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table child = (Table)foreignKey.Root.Instance;
            Table parent = ArrayUtil.Find<Table>(child.GeneratedTables, (t) => (t.TableName == fk.ParentTableName));
            if (parent == null)
                throw new InvalidOperationException("Code is not generated for table: " + fk.ParentTableName);

            // new string[] { "EmployeeID" }, new string[] { "ReportsTo" }
            StringBuilder ctor = new StringBuilder();
            List<string> parentPkColumns = new List<string>();
            List<string> childFkColumns = new List<string>();
            for (int i=0; i<fk.ChildColumns.Count; i++)
            {
                string pkColName = fk.ParentColumns[i];
                //var pkColumn = ArrayUtil.Find<TableField>(parent.Fields, (f) => (f.ColumnName == pkColName));
                string getPkColNameOverride = string.Format("{0}.GetColumnNameOverride(\"{1}\", \"{2}\") ?? \"{3}\"", catalogClassFullName, parent.TableName, pkColName, pkColName);
                parentPkColumns.Add(getPkColNameOverride);

                string fkColName = fk.ChildColumns[i];
                //var fkColumn = ArrayUtil.Find<TableField>(child.Fields, (f) => (f.ColumnName == fkColName));
                var getFkColNameOverride = string.Format("{0}.GetColumnNameOverride(\"{1}\", \"{2}\") ?? \"{3}\"", catalogClassFullName, child.TableName, fkColName, fkColName);
                childFkColumns.Add(getFkColNameOverride);
            }

            ctor.Append("new string[] { ");
            ctor.Append(ArrayUtil.Concat(parentPkColumns, ", "));
            ctor.Append(" }, new string[] { ");
            ctor.Append(ArrayUtil.Concat(childFkColumns, ", "));
            ctor.Append(" }");

            return ctor.ToString();
        }

        /// <summary>Gets parent table name.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string ParentTableName(this ITemplateInputComponent foreignKey)
        {
            return ((ForeignKey)foreignKey.Instance).ParentTableName;
        }

        /// <summary>Gets child class prefix. Pascal case.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string ChildClassPrefix(this ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            return DbComponentExtensionMethods.GetClassPrefix(fk.ChildTable.TableName, foreignKey.CurrentTemplateData);
        }

        /// <summary>Gets child class prefix in camel case.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string ChildClassPrefixCamelCase(this ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            string className = DbComponentExtensionMethods.GetClassPrefix(fk.ChildTable.TableName, foreignKey.CurrentTemplateData);
            return NamingConventionHelper.ToCamelCase(className);
        }

        /// <summary>Gets child object FK part property name.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="index">Zero based index that specifies FK part.</param>
        public static string ChildFkPropertyName(this ITemplateInputComponent foreignKey, int index)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            TableField[] childFields = fk.GetChildFkFields();
            return (childFields.Length > index) ? childFields[index].Names.PublicMember : "";
        }

        /// <summary>Gets child object PK part property name.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="index">Zero based index that specifies PK part.</param>
        public static string ChildPkPropertyName(this ITemplateInputComponent foreignKey, int index)
        {
            Table child = FkCriteria.GetChildTable(foreignKey);
            int currPkIndex = 0;
            foreach (TableField col in child.Fields)
            {
                if (col.IsPrimaryKeyPart)
                {
                    if (currPkIndex == index)
                        return col.Names.PublicMember;
                    else
                        currPkIndex++;
                }
            }

            return "";
        }

        /// <summary>Gets child object PK part property data type.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="index">Zero based index that specifies PK part.</param>
        public static string ChildPkPropertyType(this ITemplateInputComponent foreignKey, int index)
        {
            Table child = FkCriteria.GetChildTable(foreignKey);
            int currPkIndex = 0;
            foreach (TableField col in child.Fields)
            {
                if (col.IsPrimaryKeyPart)
                {
                    if (currPkIndex == index)
                        return col.Types.Type;
                    else
                        currPkIndex++;
                }
            }

            return "";
        }

        /// <summary>Ensures unique child ordinal if child refences parent object via multiple FKs, ie. parallel relations. Empty string is returned for 1st FK, one-based ordinal if its 2nd, 3rd or later FK to the same table</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string UniqueChildSuffix(this ITemplateInputComponent foreignKey)
        {
            // Empty string if this is 1st FK to parent table. Ordinal if its 2nd, 3rd or later FK to the same table, ie. parallel relation.
            Table child = FkCriteria.GetChildTable(foreignKey);
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            int ordinal = GetFkOrdinalForParallelRelation(child, fk);
            string suffix = (ordinal <= 1) ? "" : ordinal.ToString(CultureInfo.InvariantCulture);
            return suffix;
        }

        private static int GetFkOrdinalForParallelRelation(Table child, ForeignKey fk)
        {
            int ordinal = 1;
            foreach (ForeignKey rel in child.ForeignKeys)
            {
                if (IsSameParentAndEqualColumns(fk, rel))
                    return ordinal;

                bool isSameParentButOtherFk = (fk.ParentTableName == rel.ParentTableName);
                if (isSameParentButOtherFk)
                    ordinal++;
            }

            throw new InvalidOperationException("Should not get here.");
        }

        private static bool IsSameParentAndEqualColumns(ForeignKey fk1, ForeignKey fk2)
        {
            if (fk1.ParentTableName != fk2.ParentTableName)
                return false;

            if (fk1.ChildColumns.Count != fk2.ChildColumns.Count)
                return false;

            for (int i = 0; i < fk1.ChildColumns.Count; i++)
            {
                if (fk1.ChildColumns[i] != fk2.ChildColumns[i])
                    return false;
            }

            return true;
        }

        /// <summary>Gets first textual property of parent table or any other if no text property is found.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static string Parent1stTextPropertyOrAny(this ITemplateInputComponent foreignKey)
        {
            Table childTable = (Table)foreignKey.Parent.Instance;
            Table parentTable = GetParentTable(childTable.GeneratedTables, foreignKey);
            foreach (TableField field in parentTable.Fields)
            {
                if (field.Length >= 1)
                    return field.Names.PublicMember;
            }

            return parentTable.Fields[0].Names.PublicMember;
        }

        private static Table GetParentTable(Table[] generatedTables, ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            foreach (Table tbl in generatedTables)
            {
                if (tbl.TableName == fk.ParentTableName)
                    return tbl;
            }

            return null;
        }

        #endregion

        #region FK templates.

        static IForeignKeyCodeRenderer ParentTemplate = new FkCodeRenderer("", true, "Parent", "", false, new string[0]);
        static IForeignKeyCodeRenderer RecursiveTemplate = new FkCodeRenderer("", true, "Superior", "", false, new string[0]);
        static IForeignKeyCodeRenderer ParallelFkTemplate = new FkCodeRenderer("", true, "Parent", "_", true, new string[0]);

        private static string GetRecursiveParentSuffix(ITemplateData currTemplate)
        {
            string suffix = currTemplate.GetSetting(CommonVariables.RecursiveParentSuffix);
            if (suffix == null)
                suffix = currTemplate.GetVariable(CommonVariables.RecursiveParentSuffix);

            if (suffix == null)
                suffix = "Superior";

            return suffix;
        }

        private static string GetParallelParentSuffix(ITemplateData currTemplate)
        {
            string suffix = currTemplate.GetSetting(CommonVariables.ParallelParentSuffix);
            if (suffix == null)
                suffix = currTemplate.GetVariable(CommonVariables.ParallelParentSuffix);

            if (suffix == null)
                suffix = "Parent";

            return suffix;
        }

        private static string GetParentSuffix(ITemplateData currTemplate)
        {
            string suffix = currTemplate.GetSetting(CommonVariables.ParentSuffix);
            if (suffix == null)
                suffix = currTemplate.GetVariable(CommonVariables.ParentSuffix);

            if (suffix == null)
                suffix = "Parent";

            return suffix;
        }

        #endregion

        #region FK util.

        private static readonly string[] EmptyStringArray = new string[0];
        private static Dictionary<string, string[]> ParsedArrayCache = new Dictionary<string, string[]>();

        internal static string[] ParseSemicolDelimitedArray(string semicolDelimited)
        {
            if (string.IsNullOrEmpty(semicolDelimited))
                return EmptyStringArray;
            else if (ParsedArrayCache.ContainsKey(semicolDelimited))
                return ParsedArrayCache[semicolDelimited];
            else
                return ParseAndCacheSemicolDelimitedArray(semicolDelimited);
        }

        private static string[] ParseAndCacheSemicolDelimitedArray(string semicolDelimited)
        {
            string[] splitItems = semicolDelimited.Split(';');
            List<string> nonEmptyItems = new List<string>();
            foreach (string item in splitItems)
            {
                if (!string.IsNullOrEmpty(item))
                    nonEmptyItems.Add(item);
            }

            string[] arrItems = nonEmptyItems.ToArray();
            ParsedArrayCache[semicolDelimited] = arrItems;
            return arrItems;
        }

        internal static ForeignKey GetFkContainingChildField(Table tbl, TableField fkPart)
        {
            foreach (ForeignKey fk in tbl.ForeignKeys)
            {
                foreach (string childFieldName in fk.ChildColumns)
                {
                    if (childFieldName == fkPart.ColumnName)
                        return fk;
                }
            }

            return null;
        }

        private static void GetTableAndFkFromInputComponent(ITemplateInputComponent fkOrField, out ForeignKey fk, out Table tbl)
        {
            if (fkOrField.Type == DbComponentType.FK)
            {
                fk = (ForeignKey)fkOrField.Instance;
                tbl = (Table)fkOrField.Parent.Instance;
            }
            else if (fkOrField.Type == DbComponentType.Field)
            {
                TableField field = (TableField)fkOrField.Instance;
                tbl = (Table)fkOrField.Parent.Instance;
                fk = GetFkContainingChildField(tbl, field);
            }
            else
            {
                throw new ArgumentException("Unsupported component type.");
            }
        }

        private static bool IsRecursiveRelation(ForeignKey fk)
        {
            bool equalChildAndParentTable = (fk.ChildTable.TableName == fk.ParentTableName);
            return equalChildAndParentTable;
        }

        private static bool IsParallelRelation(Table childTable, string parentTableName)
        {
            int fkToParentCount = 0;
            foreach (ForeignKey fk in childTable.ForeignKeys)
            {
                if (fk.ParentTableName == parentTableName)
                    fkToParentCount++;
            }

            bool isParallel = (fkToParentCount > 1);
            return isParallel;
        }

        private static string GetFkPropertyName(ForeignKey fk)
        {
            string fkPropertyName = "FK";
            TableField[] childFields = fk.GetChildFkFields();
            foreach (TableField fkField in childFields)
                fkPropertyName += "_" + fkField.Names.PublicMember;

            return fkPropertyName;
        }

        private static string GetPublicParentPkProperty(string fkPartColName, ForeignKey fk, Table childTable)
        {
            int idxParentTable = childTable.IndexOfGeneratedTable(fk.ParentTableName);
            //Table parentTable = ArrayUtil.Find<Table>(childTable.GeneratedTables, (t) => (t.TableName == fk.ParentTableName));            
            Table parentTable = childTable.GeneratedTables[idxParentTable];
            int partIndex = ArrayUtil.FindIndex<string>(fk.ChildColumns, (col) => (col == fkPartColName));
            string parentPkPartName = fk.ParentColumns[partIndex];
            TableField parentPkPart = ArrayUtil.Find<TableField>(parentTable.Fields, (f) => (f.ColumnName == parentPkPartName));

            return parentPkPart.Names.PublicMember;
        }

        #endregion
    }

    /// <summary>Contains common extension methods for all DB/ORM input components which may be used in templates.</summary>
    public static class DbComponentExtensionMethods
    {
        #region SuffixReplacementMap - table util.

        /// <summary>The name of the variable which contains suffix replacement map, ie. semicolon delimited rules for table name suffix replacement, eg: "ies"->"second";"s"->""</summary>
        public const string SuffixReplacementMapVariableName = "SuffixReplacementMap";

        internal static readonly IClassItemNamingConvention ClassItemTemplate = new DefaultNamingConvention();

        internal static string GetClassPrefix(string tableName, ITemplateData currTemplate)
        {
            string className = ClassItemTemplate.GenerateClassName(tableName);
            if (currTemplate != null)
            {
                string suffixReplacementMap = currTemplate.GetVariable(SuffixReplacementMapVariableName);
                if (!string.IsNullOrEmpty(suffixReplacementMap))
                    className = TextUtil.ReplaceSuffixes(className, suffixReplacementMap);
            }

            return className;
        }

        #endregion        

        #region SplitNameToWords.

        /// <summary>Splits <see cref="ITemplateInputComponent.Name"/> to words.</summary>
        /// <param name="input">Input component whose name is first converted to camel case and then split to words.</param>
        public static string SplitNameToWords(this ITemplateInputComponent input)
        {
            return SplitNameToWords(input.Name);
        }

        /// <summary>Splits input string to words.</summary>
        /// <param name="inputName">Input string which is first converted to camel case and then split to words.</param>
        public static string SplitNameToWords(this string inputName)
        {
            string camelCase = NamingConventionHelper.ToCamelCase(inputName);
            StringBuilder words = new StringBuilder();
            for (int i = 0; i < camelCase.Length; i++ )
            {
                char c = camelCase[i];
                if (char.IsUpper(c))
                {
                    if (!IsUpper(TryGet(camelCase, i-1)))
                        words.Append(' ');

                    words.Append(char.ToLowerInvariant(c));
                }
                else
                {
                    words.Append(c);
                }
            }

            if (words.Length > 0)
                words[0] = char.ToUpperInvariant(words[0]);

            return words.ToString();
        }

        private static char? TryGet(string str, int index)
        {
            bool isWithinBounds = (index >= 0) && (index < str.Length);
            return (isWithinBounds) ? (char?)str[index] : null;
        }

        private static bool IsUpper(char? c)
        { 
            return c.HasValue && char.IsUpper(c.Value);
        }

        #endregion
    }

    /// <summary>Contains methods/predicates which evaluate table criteria.</summary>
    public static class TableCriteria
    {
        #region Table input component criteria.

        /// <summary>Returns <b>true</b> if table has foreign keys.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasForeignKeys(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            bool shouldHave = !ArrayUtil.IsNullOrEmpty(tbl.ForeignKeys);
            if (shouldHave)
            {
                // Exists ForeignKey from tbl.ForeignKeys where Exists Table from tbl.GeneratedTables where TableName == fk.ParentTableName.
                bool isAtLeastOneParentTableGenerated = ArrayUtil.Exists<ForeignKey>(tbl.ForeignKeys, (fk) => tbl.IsTableGenerated(fk.ParentTableName));

                return isAtLeastOneParentTableGenerated;
            }

            return false;
        }

        /// <summary>Returns <b>false</b> if table has foreign keys.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasNoForeignKeys(ITemplateInputComponent table)
        {
            return !HasForeignKeys(table);
        }

        /// <summary>Returns <b>true</b> if table/view has primary key.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasPrimaryKey(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            return (tbl.NumberOfPrimaryKeyFields > 0);
        }

        /// <summary>Returns <b>false</b> if table/view has primary key.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasNoPrimaryKey(ITemplateInputComponent table)
        {
            return !HasPrimaryKey(table);
        }

        /// <summary>Returns <b>true</b> if table is referenced by other tables.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasChildren(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            var children = tbl.GetChildTables();
            return (children.Length > 0);
        }

        /// <summary>Returns <b>true</b> if table PK is constucted of a single integer column.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasIntegerPK(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;            
            if (tbl.NumberOfPrimaryKeyFields == 1)
            {
                var valuePkFields = ArrayUtil.FindAll<List<TableField>, TableField>(tbl.Fields, (col) => (col.IsPrimaryKeyPart && col.Types.IsValueType));
                if (valuePkFields.Count == 1)
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if table PK is constucted of a single textual (char or varchar) column.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasTextualPK(ITemplateInputComponent table)
        {            
            Table tbl = (Table)table.Instance;
            if (tbl.NumberOfPrimaryKeyFields == 1)
            {
                // Target .NET type may not be known at this time so we have to use the native DB type.
                var textPkFields = ArrayUtil.FindAll<List<TableField>, TableField>(tbl.Fields, (col) => (col.IsPrimaryKeyPart && col.Types.NativeDbType.ToLowerInvariant().Contains("char")));
                if (textPkFields.Count == 1)
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if table contains the specified column.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        /// <param name="columnName">Column name. Case is ignored.</param>
        public static bool HasColumn(ITemplateInputComponent table, string columnName)
        {
            Table tbl = (Table)table.Instance;
            string upperName = columnName.ToLowerInvariant();
            foreach (TableField col in tbl.Fields)
            {
                if (col.ColumnName.ToLowerInvariant() == upperName)
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if input component is a view.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool IsView(ITemplateInputComponent table)
        {
            Table tblOrView = table.Instance as Table;
            if (tblOrView == null)
                return false;

            return tblOrView.IsView;
        }

        /// <summary>Returns <b>true</b> if input component is not a view.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool IsNotView(ITemplateInputComponent table)
        {
            Table tblOrView = table.Instance as Table;
            if (tblOrView == null)
                return true;

            return !tblOrView.IsView;
        }

        /// <summary>Returns <b>true</b> if input component is a view.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool IsTable(ITemplateInputComponent table)
        {
            Table tblOrView = table.Instance as Table;
            if (tblOrView == null)
                return false;

            return !tblOrView.IsView;
        }

        /// <summary>Returns <b>true</b> if table contains text field.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasTextField(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            foreach (TableField col in tbl.Fields)
            {
                string dbType = col.Types.NativeDbType.ToLowerInvariant();
                bool isText = dbType.Contains("char") || dbType.Contains("text");
                if (isText)
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if table does not contain text field.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasNoTextField(ITemplateInputComponent table)
        {
            return !TableCriteria.HasTextField(table);
        }

        /// <summary>Returns <b>true</b> if all PK fields are also FK fields.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool IsExtensionTable(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            var pkFields = tbl.PrimaryKey;
            if (ArrayUtil.IsNullOrEmpty(pkFields))
                return false;

            foreach (IDbColumn pkPart in tbl.PrimaryKey)
            {
                if (!pkPart.IsForeignKeyPart)
                    return false;
            }

            return true;
        }

        /// <summary>Returns <b>true</b> if the provided table is not extension table.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool IsNotExtensionTable(ITemplateInputComponent table)
        {
            return !IsExtensionTable(table);
        }

        /// <summary>Returns <b>true</b> if the provided table had composite primary key.</summary>
        /// <param name="table">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.Table"/> object.</param>
        public static bool HasCompositePK(ITemplateInputComponent table)
        {
            Table tbl = (Table)table.Instance;
            return (tbl.NumberOfPrimaryKeyFields > 1);
        }

        #endregion
    }

    /// <summary>Contains methods/predicates which evaluate field criteria.</summary>
    public static class FieldCriteria
    {
        #region Field input component criteria.

        /// <summary>Returns <b>true</b> if field has a value type.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsValueType(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return field.Types.IsValueType;
        }

        /// <summary>Returns <b>true</b> if field has a reference type.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsReferenceType(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return !field.Types.IsValueType;
        }

        /// <summary>Returns <b>true</b> if field is nullable.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsNullable(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return field.IsNullable;
        }

        /// <summary>Returns <b>true</b> if field is not nullable.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsNotNullable(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return !field.IsNullable;
        }

        /// <summary>Returns <b>true</b> if field is FK part.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsForeignKeyPart(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            Table tbl = (Table)tableField.Parent.Instance;
            bool isFkPartAndParentIsGenerated = false;
            if (field.IsForeignKeyPart)
            {
                ForeignKey fkOnField = FkExtensionMethods.GetFkContainingChildField(tbl, field);
                isFkPartAndParentIsGenerated = tbl.IsTableGenerated(fkOnField.ParentTableName);
            }

            return isFkPartAndParentIsGenerated;
        }

        /// <summary>Returns <b>true</b> if field is not FK part.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsNotForeignKeyPart(ITemplateInputComponent tableField)
        {
            return !IsForeignKeyPart(tableField);
        }

        /// <summary>Returns <b>true</b> if field is PK part.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsPrimaryKeyPart(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            return field.IsPrimaryKeyPart;
        }

        /// <summary>Returns <b>true</b> if field is not PK part.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsNotPrimaryKeyPart(ITemplateInputComponent tableField)
        {
            return !IsPrimaryKeyPart(tableField);
        }

        /// <summary>Returns <b>true</b> if field is textual.</summary>
        /// <param name="tableField">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.TableField"/> object.</param>
        public static bool IsText(ITemplateInputComponent tableField)
        {
            TableField field = (TableField)tableField.Instance;
            string dbType = field.Types.NativeDbType.ToLowerInvariant();
            bool isText = dbType.Contains("char") || dbType.Contains("text") || dbType.Contains("clob");
            return isText;
        }

        #region IsMatch.

        /// <summary>Creates a predicate, ie. method delegate, which checks if column name matches give regular expression.</summary>
        public static Predicate<ITemplateInputComponent> IsMatch(string regexPattern)
        {
            return new Predicate<ITemplateInputComponent>(new FieldRegexCriteria(regexPattern).IsMatch);
        }

        private class FieldRegexCriteria
        {
            Regex rex;

            public FieldRegexCriteria(string regexPattern)
            {
                this.rex = new Regex(regexPattern);
            }

            public bool IsMatch(ITemplateInputComponent tableField)
            {
                TableField field = (TableField)tableField.Instance;
                return (field != null) && this.rex.IsMatch(field.ColumnName);
            }
        }

        #endregion

        #endregion
    }

    /// <summary>Contains methods/predicates which evaluate FK criteria.</summary>
    public static class FkCriteria
    {
        #region FK input component criteria.

        /// <summary>Returns <b>true</b> if code for parent table is generated.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool IsParentEntityGenerated(ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table childTable = (Table)foreignKey.Root.Instance;
            bool isParentGenerated = childTable.IsTableGenerated(fk.ParentTableName);
            return isParentGenerated;
        }

        /// <summary>Returns <b>true</b> if child table has a single field PK.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool ChildHasSingleFieldPk(ITemplateInputComponent foreignKey)
        {
            Table childTable = GetChildTable(foreignKey);
            return (childTable.NumberOfPrimaryKeyFields == 1);
        }

        /// <summary>Returns <b>true</b> if child table has a single field integer PK.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool ChildHasSingleFieldIntegerPk(ITemplateInputComponent foreignKey)
        {
            Table childTable = GetChildTable(foreignKey);
            if (childTable.NumberOfPrimaryKeyFields == 1)
            {
                var valuePkFields = ArrayUtil.FindAll<List<TableField>, TableField>(childTable.Fields, (col) => (col.IsPrimaryKeyPart && col.Types.IsValueType));
                if (valuePkFields.Count == 1)
                    return true;
            }

            return false;
        }

        /// <summary>Returns <b>true</b> if child table contains the specified column.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        /// <param name="columnName">Column name.</param>
        public static bool ChildHasColumn(ITemplateInputComponent foreignKey, string columnName)
        {
            columnName = columnName.ToLowerInvariant();
            Table childTable = GetChildTable(foreignKey);
            foreach (TableField field in childTable.Fields)
            {
                if (field.ColumnName.ToLowerInvariant() == columnName)
                    return true;
            }

            return false;
        }

        internal static Table GetChildTable(ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table parentTable = (Table)foreignKey.Parent.Instance;
            foreach (Table tbl in parentTable.GeneratedTables)
            {
                if (tbl.TableName == fk.ChildTable.TableName)
                    return tbl;
            }

            return null;
        }

        /// <summary>Returns <b>true</b> if the provided FK represents a one-to-one relation.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool IsOneToOneRelation(ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table childTable = GetChildTable(foreignKey);
            if (fk.ChildColumns.Count != childTable.NumberOfPrimaryKeyFields)
                return false;

            foreach (string fkFieldName in fk.ChildColumns)
            {
                TableField col = childTable.GetFieldByName(fkFieldName);
                if (!col.IsPrimaryKeyPart)
                    return false;
            }

            return true;            
        }

        /// <summary>Returns <b>false</b> if the provided FK represents a one-to-one relation.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool IsNotOneToOneRelation(ITemplateInputComponent foreignKey)
        {
            return !IsOneToOneRelation(foreignKey);
        }

        /// <summary>Returns <b>true</b> if the provided FK is nullable.</summary>
        /// <param name="foreignKey">Input component whose <see cref="ITemplateInputComponent.Instance"/> property must contain a <see cref="BizElements.Generator.ForeignKey"/> object.</param>
        public static bool IsNullable(ITemplateInputComponent foreignKey)
        {
            ForeignKey fk = (ForeignKey)foreignKey.Instance;
            Table childTable = GetChildTable(foreignKey);
            foreach (string fkColName in fk.ChildColumns)
            {
                var fkPart = childTable.Columns.GetByColumnName(fkColName);
                if (!fkPart.IsNullable)
                    return false;
            }

            return true;
        }

        #endregion
    } 
}
